package com.yh.csx.crm.core.util;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class HttpUtils {
    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    public static Request newRequest() {
        return new Request();
    }

    public static String get(String url) throws Exception {
        return newRequest().get(url).executeForString();
    }

    public static class Request {
        private String method = "GET";
        private String url;
        private Map<String, String> headers = new LinkedHashMap<>();
        private Map<String, String> parameters = new LinkedHashMap<>();
        private List<File> files = new ArrayList<>();
        private Charset charset = DEFAULT_CHARSET;
        private String content;
        private String mimeType;
        private RequestConfig.Builder configBuilder = RequestConfig.custom();
        private File keystore;
        private String password;
        private ResponseHandler responseHandler;

        public Request() {
            configBuilder.setConnectTimeout(10000);
            configBuilder.setSocketTimeout(60000);
        }

        public Request request(String method, String url) {
            this.method = method;
            this.url = url;
            return this;
        }

        public Request get(String url) {
            return request("GET", url);
        }

        public Request post(String url) {
            return request("POST", url);
        }

        public Request put(String url) {
            return request("PUT", url);
        }

        public Request delete(String url) {
            return request("DELETE", url);
        }

        public Request charset(String charsetName) {
            this.charset = Charset.forName(charsetName);
            return this;
        }

        public Request charset(Charset charset) {
            this.charset = charset;
            return this;
        }

        public Request header(String name, String value) {
            this.headers.put(name, value);
            return this;
        }

        public Request headers(Map<String, String> headers) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                this.headers.put(entry.getKey(), entry.getValue());
            }
            return this;
        }

        public Request parameter(String name, String value) {
            this.parameters.put(name, value);
            return this;
        }

        public Request parameters(Map<String, String> parameters) {
            for (Map.Entry<String, String> entry : parameters.entrySet()) {
                this.parameters.put(entry.getKey(), entry.getValue());
            }
            return this;
        }

        public Request files(File... files) {
            for (File file : files) {
                this.files.add(file);
            }
            return this;
        }

        public Request mimeType(String mimeType) {
            this.mimeType = mimeType;
            return this;
        }

        public Request content(String content) {
            this.content = content;
            return this;
        }

        public Request keystore(File keystore, String password) {
            this.keystore = keystore;
            this.password = password;
            return this;
        }

        public Request connectTimeout(int connectTimeout) {
            this.configBuilder.setConnectTimeout(connectTimeout);
            return this;
        }

        public Request socketTimeout(int socketTimeout) {
            this.configBuilder.setSocketTimeout(socketTimeout);
            return this;
        }

        public Request onResponse(ResponseHandler handler) {
            this.responseHandler = handler;
            return this;
        }

        private CloseableHttpClient createClient() {
            if (url == null || url.length() < 5) throw new IllegalArgumentException("illegal url: " + url);
            String protocol = url.substring(0, 5).toLowerCase();
            if (!protocol.equals("https")) {
                return HttpClients.createDefault();
            }
            SSLContext ctx;
            try {
                if (keystore != null && password != null) {
                    ctx = SSLContexts.custom().loadTrustMaterial(keystore, password.toCharArray(), new TrustSelfSignedStrategy()).build();
                } else {
                    ctx = SSLContext.getInstance("TLS");
                    ctx.init(null, new TrustManager[]{
                            new X509TrustManager() {
                                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                                }

                                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                                }

                                public X509Certificate[] getAcceptedIssuers() {
                                    return null;
                                }
                            }
                    }, null);
                }
            } catch (Exception e) {
                throw new IllegalArgumentException("create ssl context fail: " + e.getMessage(), e);
            }
            SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(ctx, new String[]{"TLSv1"}, null,
                    (host, session) -> true);
            return HttpClients.custom().setSSLSocketFactory(factory).build();
        }

        private HttpUriRequest createRequest() {
            RequestBuilder builder = RequestBuilder.create(method);
            builder.setUri(url);
            if (charset != null) builder.setCharset(charset);
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                builder.addHeader(entry.getKey(), entry.getValue());
            }
            for (Map.Entry<String, String> entry : parameters.entrySet()) {
                builder.addParameter(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            if (charset == null) charset = Charset.forName("UTF-8");
            if (mimeType == null) mimeType = "text/plain";
            if (files.size() > 0) {
                MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
                for (File file : files) {
                    entityBuilder.addPart(file.getName(), new FileBody(file));
                }
                builder.setEntity(entityBuilder.build());
            } else if (content != null) {
                builder.setEntity(new StringEntity(content, ContentType.create(mimeType, charset)));
            }
            builder.setConfig(configBuilder.build());
            return builder.build();
        }

        public byte[] executeForBytes() throws Exception {
            try (CloseableHttpClient client = createClient()) {
                HttpUriRequest request = createRequest();
                try (CloseableHttpResponse response = client.execute(request)) {
                    if (responseHandler != null) responseHandler.handle(response);
                    StatusLine status = response.getStatusLine();
                    if (status.getStatusCode() != 200) {
                        throw new IllegalStateException("Request Fail: " + status.getStatusCode() + ", Reason: " + status.getReasonPhrase());
                    }
                    HttpEntity entity = response.getEntity();
                    return entity == null ? null : EntityUtils.toByteArray(entity);
                }
            }
        }

        public String executeForString() throws Exception {
            try (CloseableHttpClient client = createClient()) {
                HttpUriRequest request = createRequest();
                try (CloseableHttpResponse response = client.execute(request)) {
                    if (responseHandler != null) responseHandler.handle(response);
                    StatusLine status = response.getStatusLine();
                    if (status.getStatusCode() != 200) {
                        throw new IllegalStateException("Request Fail: " + status.getStatusCode() + ", Reason: " + status.getReasonPhrase());
                    }
                    HttpEntity entity = response.getEntity();
                    return entity == null ? null : EntityUtils.toString(entity, charset);
                }
            }
        }
    }

    public interface ResponseHandler {
        void handle(HttpResponse response);
    }
}